[bits 32]

; external symbols
extern _sys_config_data
extern _size_of_page_t
extern _idt_table
extern _main_tss

extern _startup32
extern _global_init_main_tss

; exported symbols
global _go32
global _go32_die_endless

; exported data symbols
global _idle_thread_stack
global _page_root_dir
global _end_of_krnl_preoccupied_area
global _flat_get_accessor_pgd_count

section .data

__gdt_table:
		dw	0,0,0,0

		dw	0FFFFh				; 4Gb - (0x100000*0x1000 = 4Gb)
		dw	0					; base address = 0
		dw	9A00h				; code read/exec
		dw	00CFh				; granularity = 4096, 386

		dw	0FFFFh				; 4Gb - (0x100000*0x1000 = 4Gb)
		dw	0					; base address = 0
		dw	9200h				; data read/write
		dw	00CFh				; granularity = 4096, 386

		dw	0ffffh				; 4GB
		dw	0					; Base = 0
		dw	0fa00h				; e=1,c=0,r=1,a=0
		dw	00cfh				; g=1,d=1,0,0

		dw	0ffffh				; 4GB
		dw	0					; Base = 0
		dw	0f200h				; e=0,ed=0,w=1,a=0
		dw	00cfh				; g=1,d=1,0,0

__tss_desc:
		dw	104, 0, 0, 0

__pdesc:
		dw	800H, 0, 0
		
__idt_desc:
		dw	7FFH, 0, 0

_idle_thread_stack:
		dd	0

_page_root_dir:
		dd	0

_end_of_krnl_preoccupied_area:
		dd	0

; end of section .data

section .text

;function go32
;void go32(void)
;
_go32:

; set the initial segment descriptor

	mov	eax, 0x10
	mov	ds,  eax
	mov	es,  eax
	mov	fs,  eax
	mov	gs,  eax
	mov	ss,  eax

	;
	; EDX = 0x12345678
	; other's must be error
	;

	cmp		edx, 0x12345678
	je		short sanity_check_passed
	add		esp, 4							; todo: check if it is right
	mov		eax, 2
	ret

	sanity_check_passed:
	;
	; calculate the key parameters including:
	; (1) start address of idle_thread_stack
	; (2) start address of page_root
	;
	
	mov		esi, _sys_config_data + 4		; sys_config.kernel_size (in bytes)
	sub		esi, 0xC0000000
	mov		ebx, [esi]
	add		ebx, 4095
	shr		ebx, 12							; ebx = krnl_size in pages
	inc		ebx								; ebx = krnl_size + sys_cfg (in page)

	mov		esi, _sys_config_data
	sub		esi, 0xC0000000
	mov		eax, [esi]						; sys_config.total_memory in KB

	shr		eax, 2							; convert to page count
	xor		edx, edx
	mov		esi, _size_of_page_t
	sub		esi, 0xC0000000
	mov		ecx, [esi]						; sizeof(page_t)
	mul		ecx								; eax = size of page_t table (in bytes)
	add		eax, 4095
	shr		eax, 12							; eax = size of page_t table (in pages)
	add		ebx, eax						; ebx = krnl_size + sys_cfg + page_t table (in page)
	add		ebx, 272						; currently, ebx -> idle thread stack start (in page)

	mov		esp, ebx
	shl		esp, 12							; initialize the stack (linear address)
	add		esp, 8192 - 4					; move to the base of stack (8K)

	call	linear_get_accessor_pgd_count	; eax = page_dir count for temp mapping area (in page)
	lea		eax, [eax + ebx + 3]			; idle thread stack (2pages) + page root entry (1page)
											; + page_dir count for temp mapping area (in page)
											; + lower 1MB + 64K(UMB) area (272pages)

	; unsigned int i;
	; for (i = 0; i * 1024 < result->[eax] + i; ++i);

	xor		edx, edx
	xor		ecx, ecx

_go32_init_LL3:

	inc		eax								; result + i
	add		ecx, 1024						; i * 1024
	inc		edx								; i++
	cmp		ecx, eax
	jb		short _go32_init_LL3

	mov		edi, _idle_thread_stack
	sub		edi, 0xC0000000
	mov		[edi], ebx						; idle thread stack start (in page count)
	inc		ebx
	inc		ebx
	
	mov		edi, _page_root_dir
	sub		edi, 0xC0000000
	mov		[edi], ebx						; page root start (in page count)
	
	mov		edi, _end_of_krnl_preoccupied_area
	sub		edi, 0xC0000000
	mov		[edi], eax						; end of kernel pre-occupied area (in page count)

	;
	; Prepare for the PAGING mode
	; (1) First, Clear page_root_dir and pte_table
	; note: ebx = page_root_dir (in page count)
	;       edx = pte_table page count

	mov		edi, ebx						; page_root_dir (page count)
	shl		edi, 12							; linear address of page_root_dir
	
	push	eax								; save eax
	xor		eax, eax

	mov		ecx, edx						; page count of pte_table
	inc		ecx								; page count of page_root_dir + pte_table
	shl		ecx, 10							; ecx = 1024 * pages
	cld
	rep	stosd								; memset to 0

	pop		eax								; restore eax (= end of kernel [page count])

	;
	; (2) Second, set the pte table
	; note: eax = kernel pre-occupied area length (in page count)
	;
	
	mov		edi, ebx						; page_root_dir (page count)
	inc		edi
	shl		edi, 12							; linear address of pte_table
	
	xor		ecx, ecx
	xor		esi, esi
	inc		esi								; esi: page table entry (P = 1)

_go32_pte_tbl_LL5:

	mov		[edi + ecx * 4], esi
	add		esi, 0x1000						; next page table entry
	inc		ecx
	cmp		ecx, eax
	jne		short _go32_pte_tbl_LL5

	;
	; set the page_root_dir
	; important: we must map both low area and high area
	; because our program is now strill running in low area
	; forget mapping low area will cause program crash immediately
	;
	
	mov		edi, ebx						; page_root_dir (page count)
	shl		edi, 12							; linear address of page_root_dir
	
	xor		ecx, ecx
	mov		esi, ebx
	inc		esi								; page count to pte_table
	shl		esi, 12
	inc		esi								; P = 1

_go32_root_dir_LL6:

	mov		[edi + ecx * 4], esi			; map low area
	mov		[edi + ecx * 4 + 768 * 4], esi	; map high area
	add		esi, 0x1000
	inc		ecx
	cmp		ecx, edx
	jne		short _go32_root_dir_LL6
	
	;
	; Enable the paging
	; note: ebx = page_root_dir (in page count)
	;

	mov		ecx, ebx
	shl		ecx, 12
	mov		cr3, ecx
		
	mov		ecx, cr0
	or		ecx, 0x80000000
	mov		cr0, ecx
		
	db		0EAH
	dd		_go32_enable_page_mode
	dw		0x08

_go32_enable_page_mode:

	; Arrage the stack
	add	esp, 0xC0000000

	;
	; remove the memory mapping of low area
	; since we are no longer needed to run
	; program in low ara
	;
	
	mov		edi, ebx						; page_root_dir (page count)
	shl		edi, 12							; linear address of page_root_dir
	add		edi, 0xC0000000					; virtual address of page_root_dir
	
	xor		ecx, ecx
	xor		esi, esi

_go32_clear_low_ara_LL7:

	mov		[edi + ecx * 4], esi			; clear low area mapping
	inc		ecx
	cmp		ecx, edx
	jne		short _go32_clear_low_ara_LL7

	mov		ecx, ebx
	shl		ecx, 12
	mov		cr3, ecx

	;--------------------- Milestone -------------------------
	; Reset GDT to default
	; Reset IDT to default interrupt
	;---------------------------------------------------------

	;
	; (1) Switch to the new GDT
	;

	mov		eax, __gdt_table
	mov		[__pdesc + 2], eax
	lgdt	[__pdesc]
	jmp		short _go32_flush_LL8

_go32_flush_LL8:

	mov		eax, 0x10
	mov		ds, eax
	mov		es, eax
	mov		fs, eax
	mov		gs, eax
	mov		ss, eax

	;
	; (2) recalculate the export variable
	;
	
	mov		eax, [_idle_thread_stack]
	shl		eax, 12							; convert page count to linear address
	add		eax, 0xC0000000					; convert linear address to virtual address
	mov		[_idle_thread_stack], eax
	
	mov		eax, [_page_root_dir]
	shl		eax, 12							; convert page count to linear address
	add		eax, 0xC0000000					; convert linear address to virtual address
	mov		[_page_root_dir], eax
	
	mov		eax, [_end_of_krnl_preoccupied_area]
	shl		eax, 12							; convert page count to linear address
	add		eax, 0xC0000000					; convert linear address to virtual address
	mov		[_end_of_krnl_preoccupied_area], eax

	;
	; (3) Load IDTR
	;

	mov		edx, go32_def_int_proc
	mov		eax, 0x08						; Kernal CS
	shl		eax,16
	mov		ax, dx
	mov		dx, 0x8F00						; DPL = 0, P = 1

	mov		edi, _idt_table
	mov		ecx, 256

_go32_set_idt_LL9:

	mov		[edi], eax
	mov		[edi + 4], edx
	add		edi, 8
	loop	_go32_set_idt_LL9

	mov		eax, _idt_table
	mov		[__idt_desc + 2], eax
	lidt	[__idt_desc]

	;
	; (4) Load TSS
	;

	mov		eax, _global_init_main_tss
	call	eax								; init the main_tss first

	mov		eax, _main_tss
	mov		[__tss_desc + 2], ax
	shr		eax, 16
	mov		[__tss_desc + 4], al
	mov		word [__tss_desc + 5], 0x0089
	mov		[__tss_desc + 7], ah
	mov		eax, 0x28
	ltr		ax

	;
	; stop the floppy motor, if it is running
	; and goto C code: startup32
	
	call	floppy_motoroff
	mov		eax, _startup32
	sti
	call	eax
	
_go32_shall_never_here:
	hlt
	jmp		short _go32_shall_never_here

_go32_die_endless:

	cli										; block interrupt forever

_go32_die:
	hlt										; sleep forever
	jmp		short _go32_die

	;	uint linear_get_accessor_pgd_count(int kb)
	;	{
	;		if (kb < 256 * 1024)
	;			return 1;
	;		else if (kb < 512 * 1024)
	;			return 2;
	;		else if (kb < 1024 * 1024)
	;			return 4;
	;		else if (kb < 2048 * 1024)
	;			return 8;
	;		else return 16;
	;	}

_flat_get_accessor_pgd_count:

	mov		eax, [_sys_config_data]
	jmp		short _go32_get_map_page_dir_cnt_LL1

	align	4
linear_get_accessor_pgd_count:

	push	esi
	mov		esi, _sys_config_data
	sub		esi, 0xC0000000
	mov		eax, [esi]						; sys_config.total_memory in KB
	pop		esi

_go32_get_map_page_dir_cnt_LL1:

	;
	; eax = total memory (in KB)
	;

	cmp		eax, 262144						; 256 * 1024
	jge		short _go32_LN8_linear_get
	mov		eax, 1
	ret

_go32_LN8_linear_get:

	cmp		eax, 524288						; 512 * 1024
	jge		short _go32_LN6_linear_get
	mov		eax, 2
	ret

_go32_LN6_linear_get:

	cmp		eax, 1048576					; 1024 * 1024
	jge		short _go32_LN4_linear_get
	mov		eax, 4
	ret

_go32_LN4_linear_get:

	push	ecx
	xor		ecx, ecx
	cmp		eax, 2097152					; 2048 * 1024
	setge	cl
	lea		ecx, [ecx * 8 + 8]
	mov		eax, ecx
	pop		ecx
	ret

; end of linear_get_map_page_dir_count

go32_def_int_proc:

	push	eax
	mov		al, 0x20
	out		0x20, al
	out		0xA0, al
	pop		eax
	iret

; end of go32_def_int_proc

floppy_motoroff:

	mov		edx, 0x3F2						; FDC_CTRL port
	mov		eax, 8
	out		dx, al
	ret

; end of floppy_motoroff